package com.hzw.saas.service.workspace.listener;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ecs.AmazonECS;
import com.amazonaws.services.ecs.AmazonECSClientBuilder;
import com.amazonaws.services.ecs.model.*;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hzw.saas.api.workspace.IWorkInstanceService;
import com.hzw.saas.api.workspace.enms.InstStatus;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.config.util.BearerTokenExtractor;
import com.hzw.saas.service.storage.config.StorageConfig;
import com.hzw.saas.service.workspace.config.ECSConfig;
import com.hzw.saas.service.workspace.mapper.AwsEcsConfigMapper;
import com.hzw.saas.service.workspace.mapper.WorkInstanceMapper;
import com.hzw.saas.service.workspace.pojo.event.*;
import com.hzw.saas.service.workspace.pojo.model.AwsEcsConfig;
import com.hzw.saas.service.workspace.pojo.model.WorkInstance;
import com.hzw.saas.service.workspace.util.SocketUtil;
import jodd.util.StringUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 监听业务开启工作实例，后面可能时异步的
 * </p>
 *
 * @author sonam
 * @since 2021/2/4 13:17
 */
@Slf4j
@Component
@AllArgsConstructor
@Order(1)
public class WorkInstanceListener {

    private final ECSConfig config;
    private final StorageConfig efsConfig;

    private final AwsEcsConfigMapper awsEcsConfigMapper;
    private final WorkInstanceMapper workInstanceMapper;
    private final IWorkInstanceService workInstanceService;
    private final BearerTokenExtractor bearerTokenExtractor;

    @Autowired(required = false)
    private HttpServletRequest request;

    /**
     * 启动
     *
     * @param workInstanceEvent
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @EventListener(StartWorkInstanceEvent.class)
    public void startWorkInstance(StartWorkInstanceEvent workInstanceEvent) {
        WorkInstance workInstance = workInstanceEvent.getWorkInstance();
        File mountDir = new File(efsConfig.getWorkSpaceCatalog(), workInstance.getUserId());
        AssertUtil.assertThrow("启动实例失败，工作空间丢失", !mountDir.exists());

        try {
            AwsEcsConfig awsEcsConfig = awsEcsConfigMapper.selectOne(Wrappers.<AwsEcsConfig>lambdaQuery().eq(AwsEcsConfig::getProductId, workInstance.getProductId()));

            AmazonECS amazonECS = initECS();
            RunTaskRequest runTaskRequest = new RunTaskRequest();
            runTaskRequest.setLaunchType(awsEcsConfig.getLaunchType());
            runTaskRequest.setTaskDefinition(workInstance.getInstTaskFamily().concat(StrUtil.COLON).concat(workInstance.getInstTaskRevision() + ""));
            runTaskRequest.setCluster(workInstance.getCluster());
            runTaskRequest.setCount(1);
            runTaskRequest.setEnableECSManagedTags(true);
            RunTaskResult runTaskResult = amazonECS.runTask(runTaskRequest);
            if (CollectionUtil.isNotEmpty(runTaskResult.getFailures())) {
                for (Failure failure : runTaskResult.getFailures()) {
                    log.error("aws error arn: {}", failure.getArn());
                    log.error("aws error detail: {}", failure.getDetail());
                    log.error("aws error reason: {}", failure.getReason());
                }
                throw DefaultSaasException.build("启动工作实例失败，集群异常");
            }
            Task task = runTaskResult.getTasks().get(0);
            workInstanceEvent.getWorkInstance().setInstTaskId(task.getTaskArn());

            //创建token文件
            this.createTokenFile(mountDir);
        } catch (Exception e) {
            //  出错就删除任务定义
            workInstanceService.deleteWorkInstance(workInstance.getInstId());
            log.error("启动工作实例", e);
            throw DefaultSaasException.build("工作实例异常，稍后重试");
        }
    }

    /**
     * 停止
     *
     * @param workInstanceEvent
     */
    @EventListener(StopWorkInstanceEvent.class)
    public void stopWorkInstance(StopWorkInstanceEvent workInstanceEvent) {
        WorkInstance workInstance = workInstanceEvent.getWorkInstance();
        AssertUtil.assertThrow("停止工作实例失败，当前实例没有启动", StrUtil.isBlank(workInstance.getInstTaskId()));
        try {
            AmazonECS amazonECS = initECS();
            StopTaskRequest stopTaskRequest = new StopTaskRequest();
            stopTaskRequest.setTask(workInstance.getInstTaskId());
            stopTaskRequest.setCluster(workInstance.getCluster());
            amazonECS.stopTask(stopTaskRequest);
        } catch (Exception e) {
            log.error("停止工作实例", e);
            throw DefaultSaasException.build("停止工作实例失败");
        }
    }

    /**
     * 删除（一般为过期删除）
     *
     * @param workInstanceEvent
     */
    @EventListener(DeleteWorkInstanceEvent.class)
    public void deleteWorkInstance(DeleteWorkInstanceEvent workInstanceEvent) {
        if (ObjectUtil.hasNull(workInstanceEvent, workInstanceEvent.getWorkInstance())) return;
        if (StrUtil.isBlank(workInstanceEvent.getWorkInstance().getInstId())) return;
        workInstanceService.stopWorkInstance(workInstanceEvent.getWorkInstance().getInstId());
    }

    /**
     * 查看工作实例
     *
     * @param selectWorkInstanceEvent
     */
    @EventListener(SelectWorkInstanceEvent.class)
    public void selectWorkInstance(SelectWorkInstanceEvent selectWorkInstanceEvent) {
        try {
            WorkInstance workInstance = selectWorkInstanceEvent.getWorkInstance();

            if (StrUtil.isBlank(workInstance.getInstTaskId())) {
                selectWorkInstanceEvent.setInstStatus(InstStatus.STOPPED);
                return;
            }

            AmazonECS client = initECS();
            AmazonEC2 amazonEC2 = initEC2();

            DescribeTasksRequest describeTasksRequest = new DescribeTasksRequest();
            describeTasksRequest.setCluster(workInstance.getCluster());
            describeTasksRequest.withTasks(workInstance.getInstTaskId());
            DescribeTasksResult describeTasksResult = client.describeTasks(describeTasksRequest);

            Task task = null;
            if (CollectionUtils.isNotEmpty(describeTasksResult.getTasks())) {
                task = describeTasksResult.getTasks().get(0);
            }

            InstStatus instStatus = null;
            if (Objects.nonNull(task)) {
                instStatus = InstStatus.instance(task.getLastStatus());
                selectWorkInstanceEvent.setInstStatus(instStatus);
            } else {
                // 出现这种情况，应该是ecs后台停止了实例，或者ec2自己挂了
                selectWorkInstanceEvent.setInstStatus(InstStatus.STOPPED);
                workInstance.setInstTaskId(null);
                workInstance.setInstStartTime(null);
                workInstanceMapper.updateById(workInstance);
            }


            if (Objects.equals(InstStatus.RUNNING, instStatus)) {
                NetworkBinding networkBinding = task.getContainers().get(0).getNetworkBindings().get(0);
                selectWorkInstanceEvent.setPort(networkBinding.getHostPort());
                log.debug("public port:" + selectWorkInstanceEvent.getPort());

                // 获取容器实例信息
                DescribeContainerInstancesRequest describeContainerInstancesRequest = new DescribeContainerInstancesRequest();
                describeContainerInstancesRequest.setCluster(workInstance.getCluster());
                describeContainerInstancesRequest.withContainerInstances(task.getContainerInstanceArn());
                DescribeContainerInstancesResult describeContainerInstancesResult = client.describeContainerInstances(describeContainerInstancesRequest);

                // 获取容器的EC2 id
                List<ContainerInstance> containerInstances = describeContainerInstancesResult.getContainerInstances();
                for (ContainerInstance containerInstance : containerInstances) {
                    // 根据EC2 id查询公共IP
                    DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
                    describeInstancesRequest.withInstanceIds(containerInstance.getEc2InstanceId());
                    DescribeInstancesResult describeInstancesResult = amazonEC2.describeInstances(describeInstancesRequest);
                    List<Reservation> reservations = describeInstancesResult.getReservations();

                    List<Instance> instances;
                    for (Reservation res : reservations) {
                        instances = res.getInstances();
                        for (Instance ins : instances) {
                            log.debug("public IPV4:" + ins.getPublicIpAddress());
                            selectWorkInstanceEvent.setIp(ins.getPublicIpAddress());
                        }
                    }
                }

                if (!SocketUtil.telnet(selectWorkInstanceEvent.getIp(), selectWorkInstanceEvent.getPort())) {
                    selectWorkInstanceEvent.setIp(null);
                    selectWorkInstanceEvent.setPort(0);
                    selectWorkInstanceEvent.setInstStatus(InstStatus.STARTING);
                }

            }

        } catch (Exception e) {
            log.error("查看工作空间实例", e);
            throw DefaultSaasException.build("查看工作空间实例失败", e);
        }
    }

    /**
     * 创建任务定义
     *
     * @param workInstanceEvent
     */
    @EventListener(RegisterWorkInstanceEvent.class)
    public void registerWorkInstance(RegisterWorkInstanceEvent workInstanceEvent) {
        try {
            /** 查询对应的工作实例*/
            WorkInstance workInstance = workInstanceEvent.getWorkInstance();
            AwsEcsConfig awsEcsConfig = workInstanceEvent.getAwsEcsConfig();
            /** 构建挂载的目录*/
            File mountDir = FileUtil.file(efsConfig.getWorkSpaceCatalog(), workInstance.getUserId());
            FileUtil.mkdir(mountDir);
            log.debug("创建efs挂载目录：{},是否创建成功：{}", mountDir.getAbsolutePath(), mountDir.exists());
            /** 注册任务定义*/
            AmazonECS amazonECS = initECS();
            workInstance.setInstTaskFamily(workInstance.getProductId().concat(StrUtil.DASHED).concat(awsEcsConfig.getDockerName()));

            RegisterTaskDefinitionRequest taskDefinition = createTaskDefinition(awsEcsConfig, workInstance);
            RegisterTaskDefinitionResult registerTaskDefinitionResult = amazonECS.registerTaskDefinition(taskDefinition);
            /**revision */
            Integer revision = registerTaskDefinitionResult.getTaskDefinition().getRevision();
            workInstanceEvent.getWorkInstance().setInstTaskRevision(revision);
            workInstanceEvent.getWorkInstance().setCluster(awsEcsConfig.getCluster());
        } catch (Exception e) {
            log.error("创建任务定义", e);
            throw DefaultSaasException.build("创建任务定义失败");
        }
    }

    public void createTokenFile(File folder) {
        if (request != null) {
            Assert.notNull(folder, "create token file error: user folder is null");
            final String tokenFileName = "TempUserAccessToken.lit";
            final String tokenValue = bearerTokenExtractor.extract(request);
            File tokenFile = new File(folder, tokenFileName);
            FileUtil.writeString(tokenValue, tokenFile, StandardCharsets.UTF_8);
        }
    }

    public RegisterTaskDefinitionRequest createTaskDefinition(AwsEcsConfig awsEcsConfig, WorkInstance workInstance) {
        // 任务定义基础信息
        RegisterTaskDefinitionRequest taskDefinition = new RegisterTaskDefinitionRequest();
        taskDefinition.setFamily(workInstance.getInstTaskFamily());
        taskDefinition.setTaskRoleArn("arn:aws-cn:iam::362851959810:role/ecsTaskExecutionRole");

        List<String> compatibilities = new ArrayList<String>();
        compatibilities.add(awsEcsConfig.getLaunchType());
        taskDefinition.setRequiresCompatibilities(compatibilities);

        // 性能限制和网络模式
        taskDefinition.setCpu(awsEcsConfig.getCpu());
        taskDefinition.setMemory(awsEcsConfig.getMemory());
        taskDefinition.setNetworkMode(awsEcsConfig.getNetworkMod());

        // 容器设置
        List<ContainerDefinition> containerDefinitions = new ArrayList<ContainerDefinition>();
        ContainerDefinition containerDefinition = new ContainerDefinition();
        containerDefinitions.add(containerDefinition);
        containerDefinition.setName(awsEcsConfig.getDockerName());  // 镜像名称
        containerDefinition.setImage(awsEcsConfig.getDockerImage()); // 镜像:版本
        containerDefinition.setMemory(Integer.parseInt(awsEcsConfig.getMemory()));  // 镜像内存
        containerDefinition.setPrivileged(true);

        // vnc 密码设置，6-8位
        // 计算instanceId的MD5，取前8位作为密码
//        KeyValuePair keyValuePair = new KeyValuePair();
//        keyValuePair.setName("VNC_PW");
//        keyValuePair.setValue(WorkspaceUtil.getInsPwd(workInstance.getInstId()));
//        containerDefinition.setEnvironment(CollectionUtil.list(false, keyValuePair));

        //端口映射
        List<PortMapping> portMappings = new ArrayList<PortMapping>();
        PortMapping portMapping = new PortMapping();
        portMapping.setHostPort(0);
        portMapping.setProtocol("tcp");
        portMapping.setContainerPort(awsEcsConfig.getDockerPort());
        portMappings.add(portMapping);

        containerDefinition.setPortMappings(portMappings);

        // 卷设置
        String filesystemId = awsEcsConfig.getFilesystemId();
        if (StringUtil.isNotBlank(filesystemId)) {
            Volume volume = new Volume();
            volume.setName("user-space");
            EFSVolumeConfiguration efsVolumeConfiguration = new EFSVolumeConfiguration();
            efsVolumeConfiguration.setFileSystemId(filesystemId);
            // ！工作空间的用户子目录，需要挂载efs的应用服务器预先创建
            efsVolumeConfiguration.setRootDirectory("workspace/" + workInstance.getUserId());
            efsVolumeConfiguration.setTransitEncryption("ENABLED");
            volume.setEfsVolumeConfiguration(efsVolumeConfiguration);

            List<Volume> volumes = new ArrayList<Volume>();
            volumes.add(volume);
            taskDefinition.setVolumes(volumes);

            // 卷映射
            List<MountPoint> mountPoints = new ArrayList<MountPoint>();
            MountPoint mountPoint = new MountPoint();
            mountPoints.add(mountPoint);
            mountPoint.setSourceVolume("user-space");
            mountPoint.setContainerPath(awsEcsConfig.getDockerMount());
            containerDefinition.setMountPoints(mountPoints);
        }

        taskDefinition.setContainerDefinitions(containerDefinitions);

        return taskDefinition;
    }

    private AmazonECS initECS() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(config.getAccessKey(), config.getSecretKey());
        return AmazonECSClientBuilder.standard().withRegion(config.getRegion()).withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials)).build();
    }

    private AmazonEC2 initEC2() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(config.getAccessKey(), config.getSecretKey());
        return AmazonEC2ClientBuilder.standard().withRegion(config.getRegion()).withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials)).build();
    }
}
